import json
import logging
import functools
import time
from typing import Any, Dict, List, Tuple, Optional, Callable, Union

from langchain.schema import HumanMessage, SystemMessage

from llm import llmConfig
from llm.prompt import getPrompt

# 配置日志
logger = logging.getLogger('ToolUtils')

# 简单的LRU缓存以减少重复的LLM调用
_cache = {}
_cache_max_size = 50
_cache_ttl = 3600  # 缓存有效期，单位秒


def cache_result(func: Callable) -> Callable:
    """简单的函数结果缓存装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 基于函数名、参数创建缓存键
        cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
        now = time.time()
        
        # 检查缓存是否存在且未过期
        if cache_key in _cache and _cache[cache_key]['expires'] > now:
            logger.debug(f"缓存命中: {cache_key}")
            return _cache[cache_key]['result']
        
        # 调用函数获取结果
        result = func(*args, **kwargs)
        
        # 存储结果到缓存
        _cache[cache_key] = {
            'result': result,
            'expires': now + _cache_ttl
        }
        
        # 如果缓存超过最大大小，删除最早的条目
        if len(_cache) > _cache_max_size:
            oldest_key = min(_cache.keys(), key=lambda k: _cache[k]['expires'])
            del _cache[oldest_key]
            
        return result
    return wrapper


@cache_result
def select_tool_with_llm_sync(tools, user_message: str) -> Tuple[str, Dict[str, Any]]:
    """同步方式使用LLM分析用户消息并选择合适的工具和参数
    
    Args:
        tools: 可用工具列表
        user_message: 用户消息
    
    Returns:
        (tool_name, arguments) 元组
    """
    # 步骤1：选择合适的工具
    tool_name = select_tool_name(tools, user_message)
    if not tool_name:
        return '', {}
    
    # 步骤2：获取工具详细信息
    selected_tool = find_tool_by_name(tools, tool_name)
    if not selected_tool:
        logger.warning(f"找不到工具: {tool_name}")
        return '', {}
    
    # 步骤3：确定工具参数
    arguments = determine_tool_parameters(selected_tool, user_message)
    
    # 步骤4：验证参数是否完整
    missing_params = validate_parameters(selected_tool, arguments)
    if missing_params:
        # 调用者需要处理这种情况，提示用户补充信息
        return tool_name, {"__missing_params": missing_params}
    
    return tool_name, arguments


def select_tool_name(tools, user_message: str) -> str:
    """步骤1：使用LLM选择合适的工具名称
    
    Args:
        tools: 可用工具列表
        user_message: 用户消息
    
    Returns:
        工具名称，若找不到合适工具则返回空字符串
    """
    if not tools or not user_message.strip():
        logger.warning("工具列表为空或用户消息为空")
        return ""
    
    # 获取LLM实例
    try:
        llm = llmConfig.getCommonLLM()
    except Exception as e:
        logger.error(f"获取LLM实例失败: {str(e)}")
        return ""
    
    # 简单提取工具基本信息
    tool_descriptions = []
    for tool in tools:
        if hasattr(tool, 'name') and hasattr(tool, 'description'):
            tool_descriptions.append({
                "name": tool.name,
                "description": tool.description or f"工具: {tool.name}"
            })
    
    if not tool_descriptions:
        logger.warning("未提取到有效工具描述")
        return ""
    
    # 构建提示
    prompt = f"""
你是一个AI助手，需要根据用户的请求选择合适的工具。请从以下工具列表中选择一个最适合处理用户请求的工具:

工具列表:
{json.dumps(tool_descriptions, ensure_ascii=False, indent=2)}

用户请求: {user_message}

请只返回所选工具的名称，不要附加任何其他内容。如果没有找到合适的工具，只返回"NONE"。
"""
    
    try:
        # 发送请求给LLM
        response_text = llm.invoke(prompt).content.strip()
        logger.info(f"LLM选择工具响应: {response_text}")
        
        # 检查是否返回NONE
        if response_text.upper() == "NONE":
            return ""
        
        # 检查返回的工具名称是否有效
        valid_tool_names = [t.name for t in tools]
        if response_text in valid_tool_names:
            return response_text
        
        # 如果返回的不是完全匹配的工具名称，尝试查找最相似的
        for tool_name in valid_tool_names:
            if tool_name.lower() in response_text.lower():
                return tool_name
        
        # 可能返回了一些额外文本，尝试提取工具名
        for tool_name in valid_tool_names:
            if tool_name in response_text:
                return tool_name
        
        logger.warning(f"LLM返回的工具名称'{response_text}'无效，有效工具: {valid_tool_names}")
        return ""
    except Exception as e:
        logger.error(f"选择工具名称失败: {str(e)}")
        return ""


def find_tool_by_name(tools, tool_name: str):
    """步骤2：根据工具名称查找工具对象
    
    Args:
        tools: 可用工具列表
        tool_name: 工具名称
    
    Returns:
        找到的工具对象，如果没找到则返回None
    """
    if not tools or not tool_name:
        return None
    
    for tool in tools:
        if hasattr(tool, 'name') and tool.name == tool_name:
            return tool
    
    return None


def determine_tool_parameters(tool, user_message: str) -> Dict[str, Any]:
    """步骤3：确定工具调用所需的参数
    
    Args:
        tool: 工具对象
        user_message: 用户消息
    
    Returns:
        参数字典或错误信息
    """
    if not tool or not hasattr(tool, 'name'):
        return {"isError": True, "message": "未找到有效工具，无法提取参数。", "content": []}
    
    try:
        llm = llmConfig.getCommonLLM()
    except Exception as e:
        logger.error(f"获取LLM实例失败: {str(e)}")
        return {"isError": True, "message": f"获取LLM实例失败: {str(e)}", "content": []}
    
    # 获取工具的全部信息（可根据实际情况过滤不必要的属性）
    tool_info = tool.__dict__ if hasattr(tool, '__dict__') else str(tool)

    # 构建提示
    prompt = f"""
你是一个AI助手，需要根据用户的请求提取参数以调用工具。

工具信息如下（包含所有可用字段）:
{json.dumps(tool_info, ensure_ascii=False, indent=2)}

用户请求: {user_message}

请从用户请求中提取调用该工具所需的参数。以JSON格式返回参数，格式为:
{{
  "参数1": "值1",
  "参数2": "值2",
  ...
}}

只返回JSON对象，不要有任何其他内容。如果某个参数在用户请求中无法确定，则不再返回参数列表，而是返回缺失的参数名称，让用户进行补写。
"""
    
    try:
        # 发送请求给LLM
        response_text = llm.invoke(prompt).content
        logger.info(f"LLM参数提取响应: {response_text}")
        
        # 尝试解析JSON
        parameters = _parse_parameters_response(response_text)
        
        # 如果没有提取到参数，返回错误信息，建议以流输出的形式展示
        if not parameters:
            error_msg = "未能从用户请求中提取到有效参数，请补充必要信息后重试。"
            logger.error(error_msg)
            return {
                "isError": True,
                "message": error_msg,
                "content": [
                    "参数提取失败，可能原因：",
                    "1. 用户输入信息不足，无法匹配工具参数。",
                    "2. 工具定义信息不完整。",
                    "3. LLM响应异常。",
                    "请补充必要参数后重试。"
                ]
            }
        
        return parameters
    except Exception as e:
        error_msg = f"确定工具参数失败: {str(e)}"
        logger.error(error_msg)
        return {
            "isError": True,
            "message": error_msg,
            "content": [
                "参数提取过程中发生异常，建议检查工具定义和用户输入。",
                str(e)
            ]
        }


def validate_parameters(tool, parameters: Dict[str, Any]) -> List[Dict[str, str]]:
    """步骤4：验证参数是否完整
    
    Args:
        tool: 工具对象
        parameters: 提取的参数
    
    Returns:
        缺失的参数列表，如果所有必需参数都存在则返回空列表
    """
    missing_params = []
    
    # 如果工具不存在或没有参数定义，返回空列表
    if not tool or not hasattr(tool, 'parameters') or not tool.parameters:
        return missing_params
    
    try:
        tool_params = tool.parameters
        
        # 检查每个必需参数
        for param_name, param_info in tool_params.items():
            is_required = False
            
            # 尝试确定参数是否必需
            if isinstance(param_info, dict) and 'required' in param_info:
                is_required = param_info['required']
            elif hasattr(param_info, 'required'):
                is_required = getattr(param_info, 'required')
            
            # 如果是必需参数但未提供
            if is_required and param_name not in parameters:
                param_desc = ""
                if isinstance(param_info, dict) and 'description' in param_info:
                    param_desc = param_info['description']
                elif hasattr(param_info, 'description'):
                    param_desc = getattr(param_info, 'description', '')
                
                missing_params.append({
                    "name": param_name,
                    "description": param_desc
                })
    except Exception as e:
        logger.error(f"验证参数失败: {str(e)}")
    
    return missing_params


def _parse_parameters_response(response_text: str) -> Dict[str, Any]:
    """解析LLM返回的参数
    
    Args:
        response_text: LLM响应文本
    
    Returns:
        参数字典
    """
    if not response_text.strip():
        return {}
    
    try:
        # 尝试清理响应文本
        clean_text = response_text.strip()
        
        # 移除可能的代码块标记
        if clean_text.startswith("```") and "```" in clean_text[3:]:
            code_content = clean_text.split("```", 2)[1]
            if code_content.startswith("json"):
                clean_text = code_content[4:].strip()
            else:
                clean_text = code_content.strip()
        
        # 提取JSON部分
        json_start = clean_text.find('{')
        json_end = clean_text.rfind('}') + 1
        if json_start >= 0 and json_end > json_start:
            json_str = clean_text[json_start:json_end]
            return json.loads(json_str)
        
        # 如果上面的方法失败，尝试直接解析
        return json.loads(clean_text)
    except Exception as e:
        logger.error(f"解析参数响应失败: {str(e)}")
        return {}


def format_tool_response(response: Optional[Any], is_error: bool = False, message: str = "") -> Dict[str, Any]:
    """格式化工具调用响应
    
    Args:
        response: 工具调用原始响应
        is_error: 是否为错误响应 
        message: 响应消息
    
    Returns:
        格式化的响应字典
    """
    if is_error:
        return {
            "isError": True,
            "content": [],
            "message": message or "工具调用失败"
        }
    
    # 提取响应内容
    content = []
    
    try:
        if response is None:
            content = []
        elif hasattr(response, 'result'):
            content = response.result
        else:
            content = response
            
        # 确保content始终是可序列化的
        if isinstance(content, dict):
            # 处理TextContent对象在字典中的情况
            processed_content = {}
            for key, value in content.items():
                if hasattr(value, 'text') and hasattr(value, '__dict__'):
                    # 如果是TextContent类型，提取text字段
                    processed_content[key] = value.text
                elif isinstance(value, (list, tuple)):
                    # 处理列表或元组
                    processed_list = []
                    for item in value:
                        if hasattr(item, 'text') and hasattr(item, '__dict__'):
                            processed_list.append(item.text)
                        else:
                            processed_list.append(item)
                    processed_content[key] = processed_list
                else:
                    processed_content[key] = value
            content = processed_content
        elif isinstance(content, list):
            # 处理内容为列表的情况
            processed_list = []
            for item in content:
                if hasattr(item, 'text') and hasattr(item, '__dict__'):
                    # 如果是TextContent类型，提取text字段
                    processed_list.append(item.text)
                elif isinstance(item, dict):
                    # 处理字典
                    processed_dict = {}
                    for k, v in item.items():
                        if hasattr(v, 'text') and hasattr(v, '__dict__'):
                            processed_dict[k] = v.text
                        else:
                            processed_dict[k] = v
                    processed_list.append(processed_dict)
                else:
                    processed_list.append(item)
            content = processed_list
        # TextContent对象单独处理
        elif hasattr(content, 'text') and hasattr(content, '__dict__'):
            content = content.text
            # 检查text是否是JSON字符串
            try:
                if isinstance(content, str) and content.strip().startswith('{'):
                    json_content = json.loads(content)
                    content = json_content
            except json.JSONDecodeError:
                # 不是有效的JSON，保持原样
                pass
        elif not isinstance(content, (str, int, float, bool, type(None))):
            try:
                # 尝试转成字典
                if hasattr(content, '__dict__'):
                    content = content.__dict__
                else:
                    content = str(content)
            except:
                content = str(content)
            
        logger.info(f"工具调用结果类型: {type(content)}")
        logger.info(f"工具调用结果内容: {content}")
        
        # 检查是否有错误信息
        error_indicator = False
        if isinstance(content, dict) and ('error' in content or 'exception' in content):
            error_indicator = True
            message = content.get('error') or content.get('exception') or message
        
        return {
            "isError": error_indicator,
            "content": content,
            "message": message or "工具调用成功"
        }
    except Exception as e:
        logger.error(f"格式化工具响应失败: {str(e)}")
        return {
            "isError": True,
            "content": [],
            "message": f"格式化响应失败: {str(e)}"
        } 